#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

#include "utils.h"
#include "patch.h"
#include "font_cache.h"

#define MAX_FILE_CACHE_SIZE 2
#define MAX_FONT_CACHE_SIZE 8
#define MAX_FONT_SUFACE_CACHE_SIZE 1000

typedef struct
{
    char *path;
    void *data;
    int64_t size;
} FontFile;

static FontFile *file_caches[MAX_FILE_CACHE_SIZE] = {0};
static mFont *font_caches[MAX_FONT_CACHE_SIZE] = {0};

char *stringDB_1624 = NULL;

char *(*GetStringDB)(int id);
int (*DrawActData)(void *a1, int a2, int a3);
int (*GRPfont_show_font_EP11ISDLSurfaceiiPKctt)(void *grpFont, void *iSurface, int a3, int a4, const char *a5, int a6, int a7);

int Utf8ToUcs2(const char *utf8, unsigned int *character)
{
    if (((utf8[0] & 0xF0) == 0xE0) && ((utf8[1] & 0xC0) == 0x80) && ((utf8[2] & 0xC0) == 0x80))
    {
        *character = ((utf8[0] & 0x0F) << 12) | ((utf8[1] & 0x3F) << 6) | (utf8[2] & 0x3F);
        return 3;
    }
    else if (((utf8[0] & 0xE0) == 0xC0) && ((utf8[1] & 0xC0) == 0x80))
    {
        *character = ((utf8[0] & 0x1F) << 6) | (utf8[1] & 0x3F);
        return 2;
    }
    else
    {
        *character = utf8[0];
        return 1;
    }
}

static void freeFontFile(FontFile *entry)
{
    if (!entry)
        return;

    if (entry->data)
        free(entry->data);
    if (entry->path)
        free(entry->path);
    free(entry);
}

static void freeFontFileCaches()
{
    int i;
    for (i = 0; i < MAX_FILE_CACHE_SIZE; i++)
    {
        if (file_caches[i])
            freeFontFile(file_caches[i]);
        file_caches[i] = NULL;
    }
}

static void freeFont(mFont *font)
{
    if (!font)
        return;

    if (font->path)
        free(font->path);
    if (font->font)
        TTF_CloseFont(font->font);
    TextSurfaceListEmpty(&font->surf_list);
    free(font);
}

static void freeFontCaches()
{
    int i;
    for (i = 0; i < MAX_FONT_CACHE_SIZE; i++)
    {
        if (font_caches[i])
            freeFont(font_caches[i]);
        font_caches[i] = NULL;
    }
}

static SDL_RWops *GetFontRWops(const char *path)
{
    if (!path)
        return NULL;

    SDL_RWops *rwops = NULL;
    void *data = NULL;
    int64_t size = 0;
    int has_cache = 0;

    int i, insert = -1;
    for (i = 0; i < MAX_FILE_CACHE_SIZE; i++)
    {
        if (!file_caches[i])
        {
            if (insert == -1)
                insert = i;
        }
        else if (file_caches[i]->path && strcasecmp(path, file_caches[i]->path) == 0)
        {
            has_cache = 1;
            data = file_caches[i]->data;
            size = file_caches[i]->size;
            goto CREATE_RWOPS;
        }
    }
    if (insert == -1) // 无空处可新建file cache
        goto ERROR;

    file_caches[insert] = (FontFile *)calloc(1, sizeof(FontFile));
    if (!file_caches[insert])
        goto ERROR;

    size = allocateReadFile(path, &data);
    if (size < 0)
        goto ERROR;
    file_caches[insert]->data = data;
    file_caches[insert]->size = size;

    file_caches[insert]->path = malloc(strlen(path) + 1);
    if (!file_caches[insert]->path)
        goto ERROR;
    strcpy(file_caches[insert]->path, path);

CREATE_RWOPS:
    rwops = SDL_RWFromMem(data, size);
    if (!rwops)
        goto ERROR;
    rwops->type = SDL_RWOPS_MEMORY_RO;

    debugPrintf("GetFontRWops: path: %s, has_cache: %d; return: %p\n", path, has_cache, rwops);
    return rwops;

ERROR:
    fatal_error("Error: Failed to load font file: %s", path);
}

static mFont *GetFont(const char *path, int ptsize)
{
    if (!path)
        return NULL;

    int i, insert = -1;
    for (i = 0; i < MAX_FONT_CACHE_SIZE; i++)
    {
        if (!font_caches[i])
        {
            if (insert == -1)
                insert = i;
        }
        else if (ptsize == font_caches[i]->ptsize && (font_caches[i]->path && strcasecmp(path, font_caches[i]->path) == 0))
        {
            debugPrintf("GetFont: path: %s, ptsize: %d, has_cache: 1, cache_index: %d; return: %p\n", path, ptsize, i, font_caches[i]);
            return font_caches[i];
        }
    }
    if (insert == -1) // 无空处可新建font cache
    {
        debugPrintf("GetFont error: Failed to insert font cache!\n");
        goto ERROR;
    }

    SDL_RWops *rwops = GetFontRWops(path);
    if (!rwops)
        goto ERROR;

    font_caches[insert] = (mFont *)calloc(1, sizeof(mFont));
    if (!font_caches[insert])
    {
        debugPrintf("GetFont error: Failed to alloc font data!\n");
        goto ERROR;
    }

    font_caches[insert]->ptsize = ptsize;

    font_caches[insert]->path = malloc(strlen(path) + 1);
    if (!font_caches[insert]->path)
    {
        debugPrintf("GetFont error: Failed to alloc path!\n");
        goto ERROR;
    }
    strcpy(font_caches[insert]->path, path);

    font_caches[insert]->font = TTF_OpenFontRW(rwops, 0, ptsize);
    if (!font_caches[insert]->font)
        goto ERROR;

    font_caches[insert]->font_size = SDL_RWsize(rwops);

    font_caches[insert]->surf_list.max_length = MAX_FONT_SUFACE_CACHE_SIZE;

    debugPrintf("GetFont: path: %s, ptsize: %d, has_cache: 0, cache_index: %d; return: %p\n", path, ptsize, insert, font_caches[insert]);
    return font_caches[insert];

ERROR:
    fatal_error("Error: Failed to load font: %s", path);
}

static SDL_Surface *GetFontSurface(mFont *font, const char *text, SDL_Color fg)
{
    if (!font || !text)
        return NULL;

    int len = strlen(text);
    if (len <= 0)
        return NULL;
    else if (len > 3)
        return TTF_RenderUTF8_Blended(font->font, text, fg);

    SDL_Surface *surface = NULL;
    uint32_t character;
    Utf8ToUcs2(text, &character);
    uint64_t key = ((uint64_t)character << 32) | (*(uint32_t *)&fg);
    TextSurfaceList *list = &font->surf_list;
    TextSurfaceListEntry *entry = TextSurfaceListGetEntryByKey(list, key);
    if (entry)
    {
        TextSurfaceListMoveEntryToFirst(list, entry);
        surface = entry->surface;
    }
    else
    {
        surface = TTF_RenderUTF8_Blended(font->font, text, fg);
        if (!surface)
            return NULL;

        entry = TextSurfaceListCreatEntry(key, surface);
        if (entry)
            TextSurfaceListAddEntryToFirst(list, entry);
    }

    SDL_Surface *res = malloc(sizeof(SDL_Surface));
    memcpy(res, surface, sizeof(SDL_Surface));
    res->flags |= SDL_DONTFREE2;

    // debugPrintf("GetFontSurface: font: %p, ptsize: %d, text: %s, current_cache_size: %d, max_cache_size: %d, return: %p\n", font, font->ptsize, text, list->length, list->max_length, res);

    return res;
}

// textquality = 0
SDL_Surface *TTF_RenderUTF8_Solid_soloader(mFont *font, const char *text, SDL_Color fg)
{
    // if (!font || !text)
    //    return NULL;
    // debugPrintf("TTF_RenderUTF8_Solid: font: %p, ptsize: %d, text: %s\n", font, font->ptsize, text);

    return GetFontSurface(font, text, fg);
}

// textquality = 1
SDL_Surface *TTF_RenderUTF8_Shaded_soloader(mFont *font, const char *text, SDL_Color fg, SDL_Color bg)
{
    // if (!font || !text)
    //    return NULL;
    // debugPrintf("TTF_RenderUTF8_Shaded: font: %p, ptsize: %d, text: %s\n", font, font->ptsize, text);

    return GetFontSurface(font, text, fg);
}

// textquality = 2
SDL_Surface *TTF_RenderUTF8_Blended_soloader(mFont *font, const char *text, SDL_Color fg)
{
    // if (!font || !text)
    //    return NULL;
    // debugPrintf("TTF_RenderUTF8_Blended: font: %p, ptsize: %d, text: %s\n", font, font->ptsize, text);

    return GetFontSurface(font, text, fg);
}

TTF_Font *TTF_OpenFontRW_soloader(SDL_RWops *src, int freesrc, int ptsize)
{
    // No used
    return NULL;
}

void TTF_CloseFont_soloader(TTF_Font *font)
{
    // Use font cache, don't close in here.
}

int GRPfont_set_font(void *grpFont, const char *filename, int ptsize)
{
    if (ptsize > 64)
        ptsize = 64;

    void *FileIO = (void *)(FILEIO_ADDRESS);
    char *path = fileIO_GetResourcePath(FileIO, filename);
    mFont *font = GetFont(path, ptsize);
    *(mFont **)((_DWORD *)grpFont + 10) = font;
    debugPrintf("GrpFont load font: path: %s, size: %d\n", path, ptsize);

    if (!font)
    {
        debugPrintf("GrpFont load font error! %s\n", TTF_GetError());
        return -1;
    }

    int *FONT_USE_GB = (int *)(LOAD_ADDRESS + 0x001C85A4);
    if (*FONT_USE_GB && font->font_size < 10 * 1024 * 1024)
    {
        path = fileIO_GetResourcePath(FileIO, "CT.ttf");
        *(mFont **)((_DWORD *)grpFont + 11) = GetFont(path, ptsize);
        debugPrintf("GrpFont load font: path: %s, size: %d\n", path, ptsize);
    }

    debugPrintf("GRPfont_set_font: %p, filename: %s, ptsize: %d, font: %p\n", grpFont, filename, ptsize, font);

    return 0;
}

int j_GRPfont_show_font_EP11ISDLSurfaceiiPKctt(void *grpFont, void *iSurface, int a3, int a4, const char *a5, int color, int a7)
{
    char *text;

    if (a5 == (char *)(LOAD_ADDRESS + 0x00035935)) // 版本号
    {
        char gOldUI = *(char *)(LOAD_ADDRESS + 0x00115F70);
        int32_t *dword_1C1D50 = (int32_t *)(LOAD_ADDRESS + 0x001C1D50);
        void *mItemPic = (void *)(LOAD_ADDRESS + 0x001C1D48);
        void *cgFont = (void *)(LOAD_ADDRESS + 0x00141694);

        // 绘出战斗介面label
        text = GetStringDB(1654);
        GRPfont_show_font_EP11ISDLSurfaceiiPKctt(cgFont, iSurface, 220, 226, text, color, a7);

        // 34=>选中， 35=>未选中
        *dword_1C1D50 = (gOldUI == 0) ? 34 : 35;
        DrawActData(mItemPic, 316, 226);
        *dword_1C1D50 = (gOldUI == 1) ? 34 : 35;
        DrawActData(mItemPic, 332, 226);

        // 绘出战斗介面value
        text = (gOldUI == 1) ? GetStringDB(1656) : GetStringDB(1655);
        GRPfont_show_font_EP11ISDLSurfaceiiPKctt(grpFont, iSurface, 420, 232, text, color, a7);
    }
    else if (a5 == stringDB_1624) // 回主选单
    {
        // 绘出结束游戏
        text = GetStringDB(1623);
        GRPfont_show_font_EP11ISDLSurfaceiiPKctt(grpFont, iSurface, 484, 104, text, color, a7);
    }

    return GRPfont_show_font_EP11ISDLSurfaceiiPKctt(grpFont, iSurface, a3, a4, a5, color, a7);
}

int initPatchFont()
{
    GetStringDB = (void *)so_symbol(&so_mod, "_Z11GetStringDBi");
    DrawActData = (void *)so_symbol(&so_mod, "_Z11DrawActDataP7ACTDATAii");

    GRPfont_show_font_EP11ISDLSurfaceiiPKctt = (void *)so_symbol(&so_mod, "_ZN7GRPfont9show_fontEP11ISDLSurfaceiiPKctt");

    hook_addr(so_symbol(&so_mod, "_ZN7GRPfont8set_fontEPKci"), (uintptr_t)&GRPfont_set_font);
    hook_addr((uintptr_t)(LOAD_ADDRESS + 0x00107690), (uintptr_t)&j_GRPfont_show_font_EP11ISDLSurfaceiiPKctt);

    return 0;
}

void deinitPatchFont()
{
    freeFontCaches();
    freeFontFileCaches();
}